﻿using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Net.Http.Json;
using System.Reflection;
using System.Windows;

namespace WpfApp.Update
{
    public class UpdateService : IAsyncDisposable
    {
        private readonly string _updateBaseUrl;
        private readonly HttpClient _httpClient;
        private readonly ILogger<UpdateService> _logger;
        private readonly PeriodicTimer _timer;

        public event EventHandler? Failed;

#if RELEASE
        private readonly TimeSpan _waitTime = TimeSpan.FromMinutes(1);
#else
        private readonly TimeSpan _waitTime = TimeSpan.FromSeconds(6);
#endif

        public UpdateService(
            IConfiguration configuration,
            IHttpClientFactory factory,
            ILogger<UpdateService> logger)
        {
            _httpClient = factory.CreateClient();
            _updateBaseUrl = configuration["UpdateBaseUrl"] ?? throw new InvalidOperationException("未设置更新服务 url");
            _httpClient.BaseAddress = new Uri(_updateBaseUrl);
            _logger = logger;
            _timer = new PeriodicTimer(_waitTime);
        }

        public async Task StartMonitoringAsync(CancellationToken stoppingToken = default)
        {
            try
            {
                while (await _timer.WaitForNextTickAsync(stoppingToken))
                {
                    await CheckAndProcessUpdateAsync(stoppingToken);
                }
            }
            catch (TaskCanceledException)
            {
                // 正常取消，无需处理
            }
            catch (Exception ex)
            {
                CurrentApplication.NotifyError("软件更新监视错误，软件将不能自动更新到新版本");
                _logger.LogError(ex, "软件更新监视异常");
                Failed?.Invoke(this, EventArgs.Empty);
            }
        }

        private async Task CheckAndProcessUpdateAsync(CancellationToken cancellationToken)
        {
            AppInformation? appInfo = null;

            try
            {
                appInfo = await GetAppInformationAsync(cancellationToken);
                var comparisonResult = VersionComparer.Compare(CurrentApplication.Version, appInfo.Version);

                if (comparisonResult == VersionComparisonResult.UpdateRequired)
                {
                    await ProcessUpdateAsync(appInfo, cancellationToken);
                }
            }
            catch (FormatException ex)
            {
                CurrentApplication.NotifyError("软件版本信息格式错误");
                _logger.LogError(ex, "版本信息错误");
            }
            catch (HttpRequestException ex)
            {
                CurrentApplication.NotifyError("更新程序获取软件版本信息错误");
                _logger.LogError(ex, "获取软件版本信息网络错误");
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                CurrentApplication.NotifyError("更新过程发生错误");
                _logger.LogError(ex, "更新处理错误");
            }
        }

        private async Task ProcessUpdateAsync(AppInformation appInfo, CancellationToken cancellationToken)
        {
            CurrentApplication.NotifyInfo($"准备更新至版本 {appInfo.Version}");
            await ReadyUpdateAsync(appInfo, cancellationToken);

            StartUpdateProcess();
            ShutdownApplication();
        }

        private void StartUpdateProcess()
        {
            var exeFile = Assembly.GetEntryAssembly()!.GetName().Name + ".exe";
            var updateExePath = Path.Combine(AppContext.BaseDirectory, exeFile);

            Process.Start(new ProcessStartInfo()
            {
                FileName = updateExePath,
                WorkingDirectory = AppContext.BaseDirectory,
                Arguments = "--update",
                UseShellExecute = true
            });
        }

        private void ShutdownApplication()
        {
            Application.Current.Dispatcher.Invoke(() =>
            {
                Application.Current.Shutdown(0);
            });
        }

        private async Task ReadyUpdateAsync(AppInformation appInfo, CancellationToken cancellationToken)
        {
            var tmpPath = Path.Combine(AppContext.BaseDirectory, "tmp");
            Directory.CreateDirectory(tmpPath); // 如果目录已存在，不会抛出异常

            var fileId = Guid.NewGuid().ToString();
            var fileName = $"{fileId}.zip";
            var filePath = Path.Combine(tmpPath, fileName);

            await DownloadFileAsync(appInfo.Url, filePath, cancellationToken);

            // 更新包解压的目录
            var extractPath = Path.Combine(tmpPath, fileId);
            var destDirectoryInfo = Directory.CreateDirectory(extractPath);

            // 提取更新包
            var extractDirectory = ExtractUpdatePackage(filePath, destDirectoryInfo);

            // 更新子模块
            await UpdateSubModuleAsync(extractDirectory);

            // 保存更新元数据
            await SaveUpdateMetadataAsync(tmpPath, appInfo, filePath, extractDirectory);
        }

        private async Task UpdateSubModuleAsync(string packageDirectory)
        {
            var newModule = Path.Combine(packageDirectory, "update");

            // 如果新模块存在，则进行替换
            if (Directory.Exists(newModule))
            {
                CurrentApplication.NotifyInfo("正在更新 update 模块...");

                var destModule = Path.Combine(AppContext.BaseDirectory, "update");

                // 删除旧的更新模块
                if (Directory.Exists(destModule))
                {
                    Directory.Delete(destModule, true);
                }

                // 复制新的更新模块
                await DirectoryCopier.CopyDirectoryAsync(newModule, destModule, true);

                CurrentApplication.NotifyInfo("update 模块更新完成");
            }
        }

        private string ExtractUpdatePackage(string zipFile, DirectoryInfo destDirectory)
        {
            CurrentApplication.NotifyMessage("正在提取更新包...");
            using var archive = ZipFile.OpenRead(zipFile);
            archive.ExtractToDirectory(destDirectory.FullName, true);

            var firstEntry = destDirectory.GetDirectories().FirstOrDefault();
            CurrentApplication.NotifyMessage("更新包提取完成");
            return firstEntry!.FullName;
        }

        private async Task SaveUpdateMetadataAsync(
            string directoryPath,
            AppInformation appInfo,
            string filePath,
            string extractDirectory)
        {
            var metadata = new
            {
                AppId = MatRecApp.AppId,
                ExeFile = Path.Combine(AppContext.BaseDirectory,
                    Assembly.GetEntryAssembly()!.GetName().Name + ".exe"),
                AppDirectory = AppContext.BaseDirectory,
                AppName = appInfo.AppName,
                Version = appInfo.Version,
                FilePath = filePath,
                ExtractDirectory = extractDirectory,
            };

            var jsonPath = Path.Combine(directoryPath, "UpdateMetadata.json");
            await File.WriteAllTextAsync(jsonPath,
                System.Text.Json.JsonSerializer.Serialize(metadata));
        }

        public async Task<AppInformation> GetAppInformationAsync(CancellationToken cancellationToken = default)
        {
            var response = await _httpClient.GetAsync(
                $"/http-file-service/api/app-info/{CurrentApplication.AppName}",
                cancellationToken);

            response.EnsureSuccessStatusCode();

            var appInfo = await response.Content.ReadFromJsonAsync<AppInformation>(cancellationToken: cancellationToken)
                ?? throw new InvalidOperationException("Invalid http request content");

            return appInfo;
        }

        private async Task DownloadFileAsync(string fileUrl, string savePath, CancellationToken cancellationToken)
        {
            CurrentApplication.NotifyInfo("正在下载更新包...");
            using var response = await _httpClient.GetAsync(fileUrl,
                HttpCompletionOption.ResponseHeadersRead,
                cancellationToken);

            response.EnsureSuccessStatusCode();

            using var contentStream = await response.Content.ReadAsStreamAsync(cancellationToken);
            using var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None);

            await contentStream.CopyToAsync(fileStream, cancellationToken);
            _logger.LogInformation("文件下载完成：{FilePath}", savePath);
            CurrentApplication.NotifyInfo("更新包下载完成");
        }

        public async ValueTask DisposeAsync()
        {
            _timer.Dispose();
            _httpClient.Dispose();
            await Task.CompletedTask;
        }
    }
}